#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#All code is GPLv3 by Alejandro <alkeon@autistici.org> Castilla

#El miedo cambia de bando como de la noche al día

from icalendar import Calendar, Event
from event_day import event_day
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from dateutil.rrule import rrule, MONTHLY, weekday
from basic_event import basic_event
from math import floor
from pytz import UTC
from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend

import operator
import warnings
import os
import sys

weekdays = ['MO','TU','WE','TH','FR','SA','SU']
# - allow different filenames

class Almanaque:

	def __init__(self):
			
		self.event_list = list()
		self.password = None

	def read_password(self):
		try:

			g = open('pass', "r")
			self.password = g.read()
			g.close()
			return True

		except:
			return False
		
	def help(self):
		print("uses: ")
		print("almanaque.py -n \t Add new entry")
		print("almanaque.py -c \t Change entry")

		print("almanaque.py -s \"DD\" \t Show events for that day")
		print("almanaque.py -s \"DD-MM\" \t Show events for that day")
		print("almanaque.py -s \"DD-MM-YYYY\" \t Show events for that day")

		print("almanaque.py -d \t Check events")
		print("almanaque.py -i \t Import calendar")
		print("almanaque.py -e \t Export calendar")
		print("almanaque.py -a NAME \t Print all about event with name")
		print("almanaque.py --daemon \t Run as daemon")
		print("almanaque.py --help \t Show this help")

	def check_arguments(self):
		if(self.read_password()):
			self.load()
		elif(not self.load()):
			key = Fernet.generate_key()
			print("Key not found.")
			print("Do you want to use this random generated key: " + key.decode('utf-8'))
			print("0.- No")
			print("1.- Yes")
			option = input()
			if(option):
				self.password = key.decode('utf-8')
				g = open('pass', "wb")
				g.write(key)
				g.close()
		
		if(len(sys.argv) == 2):
			if(sys.argv[1] == "-d"):

				name = input("Write event name\n")
				self.delete_event(name)
				self.save()

			elif(sys.argv[1] == "-c"):

				name = input("Write event name\n")
				self.delete_event(name)
				self.save()

			elif(sys.argv[1] == "-n"):
				
				event = self.create_event()
				self.insert_event(event)
				self.save()

			elif(sys.argv[1] == "-i"):
				self.event_list.clear()
				self.import_calendar()
				self.save()
			elif(sys.argv[1] == "-e"):

				self.export_calendar()

			
			elif(sys.argv[1] == "--daemon"):
				self.daemon()

			elif(sys.argv[1] == "-h" or sys.argv[1] == "--help"):
				self.help()

		else:
			if(len(sys.argv) == 3 and sys.argv[1] == "-s"):
					if(len(sys.argv[2]) == 10):
						
						start = datetime.strptime(sys.argv[2], '%d-%m-%Y')

					elif(len(sys.argv[2]) == 5):
						today = datetime.now()
						start = datetime(today.year, int(sys.argv[2][3:5]), int(sys.argv[2][:2]))

					else:
						today = datetime.now()
						start = datetime(today.year ,today.month, int(sys.argv[2][:2]))

					self.check_list(start)
			elif(len(sys.argv) == 3 and  sys.argv[1] == "-a"):

				self.show_event(sys.argv[2])

			else:	
				self.check_list(datetime.today())

	def load(self):
		try:
			g = open('calendar.sics', "rb")
			
			if(self.password is None):
				self.password = input("Write password")
			
			f = Fernet(bytes(self.password, 'utf-8'))
			try:
				file_decrypted = f.decrypt(g.read())

				gcal = Calendar.from_ical(file_decrypted.decode("utf-8") )

				for component in gcal.walk():
					if(component.name == "VEVENT"):

						summary = str(component.get('summary'))
						start = datetime.strptime(component.get('dtstart').to_ical().decode('utf-8'), '%Y%m%dT%H%M%S')
						end = datetime.strptime(component.get('dtend').to_ical().decode('utf-8'), '%Y%m%dT%H%M%S')
						stamp = datetime.strptime(component.get('dtstamp').to_ical().decode('utf-8'), '%Y%m%dT%H%M%SZ')

						event = basic_event(summary, start, stamp, component.get('rrule'),end)

						self.event_list.append(event)

				g.close()
				return True
			except:
				print("Calendar not loaded. Maybe encryption")
				g.close()
				return False

		except:
			print("calendar file not loaded")
			return False

	def change_event(self):
		i = 0
		hasEnded = True
		while(i < len(self.event_list) and hasEnded):
			if(self.event_list[i].name.find(name) >= 0):

				print("Wanna change this event?")
				print(self.event_list[i].name)
				print(self.event_list[i].start)
				print(self.event_list[i].rrule)

				option = input("0.- No\n1.- Yes\n2.- Stop\n")
				if(int(option) == 1):
					hasEnded = False
					self.event_list[i] = self.create_event()
				if(int(option) == 2):
					hasEnded = False
			i += 1


	def import_calendar(self):
		g = open('import_calendar.ics', "rb")

		gcal = Calendar.from_ical(g.read() )

		for component in gcal.walk():
			if component.name == "VEVENT":

				summary = str(component.get('summary'))
				start = datetime.strptime(component.get('dtstart').to_ical().decode('utf-8'), '%Y%m%dT%H%M%S')
				end = datetime.strptime(component.get('dtend').to_ical().decode('utf-8'), '%Y%m%dT%H%M%S')
				stamp = datetime.strptime(component.get('dtstamp').to_ical().decode('utf-8'), '%Y%m%dT%H%M%SZ')

				event = basic_event(summary, start, stamp, component.get('rrule'),end)

				self.event_list.append(event)

		g.close()
		self.save()

	def show_event(self, name):
		i = 0
		while(i < len(self.event_list)):
			if(self.event_list[i].name.find(name) >= 0):
				print(self.event_list[i].name)
				print(self.event_list[i].start)
				print(self.event_list[i].end)
				print(self.event_list[i].rrule)
				
			i += 1

	def save(self):

		cal = self.events_to_calendar()
		
		f = open('calendar.sics', 'wb')
		if(self.password is None):
			password = input("Write password")
			while(password != input("Repeat password")):
				password = input("Repeat password")
		else:
			password = self.password
		
		fer = Fernet(bytes(password, 'utf-8'))
		
		f.write(fer.encrypt(cal.to_ical()))
		f.close()

	def export_calendar(self):
		
		cal = self.events_to_calendar()

		f = open('exported_calendar.ics', 'wb')
		f.write(cal.to_ical())
		f.close()

	def events_to_calendar(self):

		cal = Calendar()
		cal.add('prodid', '-//Almanaque//')
		cal.add('version', '2.0')

		i = 0

		while(i < len(self.event_list)):
			event = Event()
			event.add('summary', self.event_list[i].get_name())
			event.add('dtstart', self.event_list[i].get_start())
			event.add('dtend', self.event_list[i].get_end())
			event.add('dtstamp', self.event_list[i].get_stamp())
			
			if(self.event_list[i].get_rrule() is not None):
				event.add('rrule', self.event_list[i].get_rrule())

			cal.add_component(event)
			i += 1
		return cal

	def daemon(self):
		i = 0
		while(i < len(self.event_list)):
			isToday = self.check_day(datetime.today(), i)
			if(isToday):
				self.notify("Today", self.event_list[i].name)
				if('COUNT' in self.event_list[i].rrule):
					self.event_list[i].rrule['COUNT'][0] = str(
						int(self.event_list[i].rrule['COUNT'][0]) - 1)

			isTomorrow = self.check_day(datetime.today() + timedelta(1) , i)
			if(isTomorrow):
				self.notify("Tomorrow", self.event_list[i].name)

				
			i += 1

	def create_event(self):

		summary = input("Write event description ")
		start_day = input("Write start day ")
		start_month = input("Write start month ")
		start_year = input("Write start year ")
		hour_and_minutes = input("Wanna write time also?\n0.- No\n1.- Yes\n")
		if(hour_and_minutes == "1"):
			
			start_hour = input("Write start hour ")
			start_minutes = input("Write start minute ")
			if(start_hour == ""):
				start_hour = today.hour;
			else:
				start_hour = int(start_hour)

			if(start_minutes == ""):
				start_minutes = today.minute;
			else:
				start_minutes = int(start_minutes)

		today = datetime.today()
		if(start_day == ""):
			start_day = today.day;
		else:
			start_day = int(start_day)

		if(start_month == ""):
			start_month = today.month;
		else:
			start_month = int(start_month)

		if(start_year == ""):
			start_year = today.year;
		else:
			start_year = int(start_year)
		
		end_day = ""
		end_month = ""
		end_year = ""

		rrule = {}
		option_one_day = True
		

		frequency = input("0.- SECONDLY\n1.- MINUTELY\n2.- HOURLY\n3.- DAILY\n4.- WEEKLY\n5.- MONTHLY\n6.- YEARLY\nn.- No\n")

		if(frequency != 'n'):
			frequency = int(frequency)
			if(frequency == 0):
				rrule['FREQ'] = ['SECONDLY']
			if(frequency == 1):
				rrule['FREQ'] = ['MINUTELY']
			if(frequency == 2):
				rrule['FREQ'] = ['HOURLY']
			if(frequency == 3):
				rrule['FREQ'] = ['DAILY']
			if(frequency == 4):
				rrule['FREQ'] = ['WEEKLY']
			if(frequency == 5):
				rrule['FREQ'] = ['MONTHLY']
			if(frequency == 6):
				rrule['FREQ'] = ['YEARLY']

		print("Want detailed options?")
		print("0.- No")
		print("1.- Yes")

		detailed = int(input())
		if(detailed):
			option_one_day = int(input("One day event\n0.- No\n1.- Yes\n"))
			if(not option_one_day):

				end_day = int(input("Write end day "))
				end_month = int(input("Write end month "))
				end_year = int(input("Write end year "))

			count = int(input("Ocurrences of event (use 0 for None)\n"))
			if(count > 0):
				rrule['COUNT'] = count

			option_until = int(input("Limited by a date\n0.- No\n1.- Yes\n"))
			if(option_until):
				day = int(input("Limiting day "))
				month = int(input("Limiting month "))
				year = int(input("Limiting year "))
				date = datetime(year=year, month=month, day=day)
				rrule['UNTIL'] = date

			interval = int(input("Interval of repeat (use 0 for None)\n"))
			if(interval > 0 and interval < 10):
				rrule['INTERVAL'] = interval

			bymonth = input("Limit by month\n type n to no\n")
			if(bymonth != 'n'):
				bymonth = int(bymonth)
				rrule['BYMONTH'] = []
				while(bymonth >= 0 and bymonth < 13):
					rrule['BYMONTH'].append(bymonth)
					bymonth = int(input("Limit by month\n"))

			bymonthday = input("Limit by month day\n type n to no\n")
			if(bymonthday != 'n'):
				bymonthday = int(bymonthday)
				rrule['BYMONTHDAY'] = []
				while(bymonthday >= 0 and bymonthday < 32):
					rrule['BYMONTHDAY'].append(bymonthday)
					bymonthday = int(input("Limit by month day\n"))
				

			byday = input("Limit by day\n type n to no\n")
			if(byday != 'n'):
				i = 1
				rrule['BYDAY'] = [weekdays[int(byday[0])]]
				while(i < len(byday)):
					rrule['BYDAY'].append(weekdays[int(byday[i])])
					i += 1

			bysecond = input("Limit by second\n type n to no\n")
			if(bysecond != 'n'):
				bysecond = int(bysecond)
				rrule['BYSECOND'] = []
				while(bysecond >= 0 and bysecond <= 59):
					rrule['BYSECOND'].append(bysecond)
					bysecond = input("Limit by second\n");
					bysecond = int(bysecond)

			byweekno = input("Limit by week\n type n to no\n")
			if(byweekno != 'n'):
				i = 1
				rrule['BYWEEKNO'] = [int(byweekno[0])]
				while(i < len(byweekno)):
					rrule['BYWEEKNO'].append(int(byweekno[i]))
					i += 1

			byyearday = input("Limit by day of the year\n type n to no\n")
			if(byyearday != 'n'):
				byyearday = int(byyearday)
				rrule['BYYEARDAY'] = []
				while(byyearday >= 0 and byyearday < 367):
					rrule['BYYEARDAY'].append(byyearday)
					byyearday = input("Limit by day of the year\n");
					byyearday = int(byyearday)

		if(hour_and_minutes == "0"):
			startt = datetime(year=start_year, month=start_month, day=start_day)
		else:
			startt = datetime(year=start_year, month=start_month, day=start_day, hour= start_hour, minute=start_minutes)

		if(option_one_day):

			event = basic_event(summary, startt, datetime.today(), rrule)
		else:

			endt = datetime(year=end_year, month=end_month, day=end_day)
			event = basic_event(summary, startt, datetime.today(), rrule, endt)

		return event

	def insert_event(self, event):
		self.event_list.append(event)

	#Check end date

	def check_end(self, date, i):

		if(self.is_same_day(self.event_list[i].end, date)):
			return True

		elif(self.event_list[i].rrule is not None and 'FREQ' in self.event_list[i].rrule):
			if(not self.check_recurrence(date, i)):
				if(self.check_recurrence(date - timedelta(1), i)):
					return True
				else:
					return False
			else:
				return False
		else:
			return False

	def check_day(self, date, i):
		isToday = False
		greaterThanStart = False
		lesserThanEnd = False

		#Check if is it today

		if(self.event_list[i].start <= date):
			greaterThanStart = True
		if(self.event_list[i].end > date):
			lesserThanEnd = True
		isToday = greaterThanStart and lesserThanEnd

		if(greaterThanStart):
			#Check if is today by recurrence

			if((not isToday and self.event_list[i].rrule is not None and 'FREQ' in self.event_list[i].rrule) or isToday):
			
				return self.check_recurrence(date, i)

	#Throw notifications

	def notify(self, day, name):
		try:
			#Desktop notifications
			os.system("notify-send '"+ day +"' '" + name + "'")

		except:
			#Termux (Android)
			os.system("termux-notification --title '"+ day +"' --content '" + name + "'")

	def check_list(self, check_date):
		today_date = check_date

		print("Right now " + str(check_date))

		i = 0

		past_events = []
		today = []
		tomorrow = []
		next_events = []

		while(i < len(self.event_list)):
			#Load past events

			day = 0
			while(day < 8):

				isThisDay = self.check_end(today_date - timedelta(day), i)
				if(isThisDay):
					past_events.append(event_day(self.event_list[i], day))
				day += 1

			#Load today events

			isToday = self.check_day(today_date, i)
			if(isToday):
				if('COUNT' in self.event_list[i].rrule):
					self.event_list[i].rrule['COUNT'][0] = str(int(self.event_list[i].rrule['COUNT'][0]) - 1)
				today.append(self.event_list[i])
			
			#Load tomorrow events

			isTomorrow = self.check_day(today_date + timedelta(1), i)
			if(isTomorrow and not isToday):
				tomorrow.append(self.event_list[i])

			#Load next events

			day = 2
			while(day < 8):
				isThisDay = self.check_day(today_date + timedelta(day), i)
				if(isThisDay):
					next_events.append(event_day(self.event_list[i], day))

				day += 1
			i += 1

		#Show past events

		past_events.sort(key=operator.attrgetter('days'), reverse=True)

		i = 0

		if(len(past_events) > 0):
			print("---PAST EVENTS----")
		while(i < len(past_events)):

			print(past_events[i].event.name + " | " + str(past_events[i].days) + " days")
			i += 1

		#Show today events

		i = 0
		if(len(today) > 0):
			print("----TODAY EVENTS--")
		while(i < len(today)):

			print(today[i].name)
			i += 1

		#Show tomorrow events

		i = 0
		if(len(tomorrow) > 0):
			print("--TOMORROW EVENTS--")
		while(i < len(tomorrow)):
			print(tomorrow[i].name)
			i += 1

		#Show next events

		next_events.sort(key=operator.attrgetter('days'))

		i = 0

		if(len(next_events) > 0):
			print("-----NEXT EVENTS---")
		while(i < len(next_events)):

			print(next_events[i].event.name + " | " + str(next_events[i].days) + " days")
			i += 1


	#Check recurrence

	def check_recurrence(self, date, i):
		recurrence = True
		recurrence = recurrence and self.count(i)
		recurrence = recurrence and self.until(i, date)

		recurrence = recurrence and self.bymonth(i, date)
		recurrence = recurrence and self.bymonthday(i, date)
		recurrence = recurrence and self.byday(i, date)
		recurrence = recurrence and self.byyear(i, date)
		recurrence = recurrence and self.bysecond(i, date)
		recurrence = recurrence and self.byweekno(i, date)
		recurrence = recurrence and self.byyearday(i, date)

		recurrence = recurrence and self.freq(i, date)
		return recurrence

	#Check COUNT tag

	def count(self, i):
		if('COUNT' not in self.event_list[i].rrule):
			return True
		elif(int(self.event_list[i].rrule['COUNT'][0]) > 0):
			return True
		else:
			return False

	#Check UNTIL tag

	def until(self, i, date):
		
		if('UNTIL' not in self.event_list[i].rrule):
			return True
		elif (datetime.strptime(str(self.event_list[i].rrule['UNTIL']), '[datetime.datetime(%Y, %m, %d, %H, %M)]') > date):
			return True
		else:
			return False

	#Check BYMONTH tag

	def bymonth(self, i, date):

		if('BYMONTH' not in self.event_list[i].rrule):
			return True
		elif (self.event_list[i].rrule['BYMONTH'][0] == date.month):
			return True
		else:
			return False

	#Check BYMONTHDAY tag

	def bymonthday(self, i, date):

		if('BYMONTHDAY' not in self.event_list[i].rrule):
			return True
		elif (self.event_list[i].rrule['BYMONTHDAY'][0] == date.day):
			return True
		else:
			return False

	#Check BYDAY tag

	def byday(self, i, date):
		if('BYDAY' not in self.event_list[i].rrule):
			return True
		elif (self.check_byday_array(self.event_list[i].rrule['BYDAY'], date.weekday(), date)):
			return True
		else:
			return False

	def weekday_number_to_string(self, number):
		if(number == 0):
			return 'MO'
		elif(number == 1):
			return 'TU'
		elif(number == 2):
			return 'WE'
		elif(number == 3):
			return 'TH'
		elif(number == 4):
			return 'FR'
		elif(number == 5):
			return 'SA'
		elif(number == 6):
			return 'SU'
		else:
			return 'MO'

	def check_week_number(self, day_number, week_number, date):
		r = rrule(MONTHLY, count=1, byweekday=weekday(day_number),
			bysetpos=int(week_number), dtstart=date.replace(day=1))
	
		if(self.is_same_day(r[0], date)):
			return True
		else:
			return False
	
	def check_byday_array(self, array, day, date):
		result = False
		i = 0
		while(i < len(array)):
			week = True
			is_day = False
			if(array[i][0] == '1' or array[i][0] == '2' or array[i][0] == '3' or array[i][0] == '4' or array[i][0] == '5'):
				week = self.check_week_number(day, array[i][0], date)

			if(array[i] == self.weekday_number_to_string(day) or array[i][1:] == self.weekday_number_to_string(day)):
				is_day = True
			if(week and is_day):
				result = True
			i += 1

		return result

	#Check BYYEAR tag

	def byyear(self, i, date):

		if('BYYEAR' not in self.event_list[i].rrule):
			return True
		elif (self.event_list[i].rrule['BYYEAR'] == date.year):
			return True
		else:
			return False

	#Check BYSECOND tag

	def bysecond(self, i, date):

		if('BYSECOND' not in self.event_list[i].rrule):
			return True
		elif (self.event_list[i].rrule['BYSECOND'] == date.second):
			return True
		else:
			return False

	#Check BYWEEKNO tag

	def byweekno(self, i, date):

		if('BYWEEKNO' not in self.event_list[i].rrule):
			return True
		elif (self.event_list[i].rrule['BYWEEKNO'] == date.isocalendar()[1]):
			return True
		else:
			return False

	#Check BYYEARDAY tag

	def byyearday(self, i, date):
		if('BYYEARDAY' not in self.event_list[i].rrule):
			return True
		elif (self.event_list[i].rrule['BYYEARDAY'] == date.timetuple().tm_yday):
			return True
		else:
			return False

	#Check FREQ tag

	def freq(self, i, date):

		if('FREQ' not in self.event_list[i].rrule):
			return True
		elif ('YEARLY' in self.event_list[i].rrule['FREQ']):
			return self.freq_yearly(i, date)

		elif('MONTHLY' in self.event_list[i].rrule['FREQ']):
			return self.freq_monthly(i, date)

		elif('WEEKLY' in self.event_list[i].rrule['FREQ']):
			return self.freq_weekly(i, date)

		elif('DAILY' in self.event_list[i].rrule['FREQ']):
			is_valid = True
			if('INTERVAL' in self.event_list[i].rrule):
				
				if((date - self.event_list[i].start).days % self.event_list[i].rrule['INTERVAL'][0] != 0):
					return False
				else:
					return True
			else:
				return True
		else:
			return True

	#Check tags realted with FREQ when it's yearly

	def freq_yearly(self, i, date):
		is_valid = True
		if('INTERVAL' in self.event_list[i].rrule):
			years = self.event_list[i].start.year - date.year
			if(years % self.event_list[i].rrule['INTERVAL'][0] != 0):
				is_valid = is_valid and False
		if('BYMONTHDAY' in self.event_list[i].rrule):
			return is_valid
		if('BYDAY' in self.event_list[i].rrule):
			return is_valid
		elif(self.event_list[i].start.day == date.day and self.event_list[i].start.month == date.month):
			return is_valid
		else:
			return False

	#Check tags realted with FREQ when it's monthly

	def freq_monthly(self, i, date):
		is_valid = True
		if('INTERVAL' in self.event_list[i].rrule):

			date_without_time = datetime(self.event_list[i].start.year,
				self.event_list[i].start.month, self.event_list[i].start.day)

			date_without_time1 = datetime(date.year, date.month, date.day)
			r = relativedelta(date_without_time, date_without_time1)
			months = r.months + r.years * 12

			if(months % self.event_list[i].rrule['INTERVAL'][0] != 0):
				is_valid = is_valid and False

		if('BYDAY' in self.event_list[i].rrule):
			return is_valid
		elif(self.event_list[i].start.day == date.day):
			return is_valid
		else:
			return False

	#Check tags realted with FREQ when it's weekly

	def freq_weekly(self, i, date):
		is_valid = True
		if('INTERVAL' in self.event_list[i].rrule):
			
			weeks = (date - self.event_list[i].start).days / 7
			if(floor(weeks) % self.event_list[i].rrule['INTERVAL'][0] != 0):
				is_valid = is_valid and False
			
		if('BYDAY' in self.event_list[i].rrule):
			return is_valid and True
		elif (self.event_list[i].start.weekday() == date.weekday()):
			return is_valid and True
		else:
			return is_valid and False
		return is_valid

	def delete_event(self, name):
		i = 0
		hasEnded = False
		while(i < len(self.event_list) and not hasEnded):
			if(self.event_list[i].name.find(name) >= 0):
				print("Wanna delete this event?")
				print(self.event_list[i].name)
				print(self.event_list[i].start)
				print(self.event_list[i].rrule)
				option = input("0.- No\n1.- Yes\n2.- Stop\n")

				if(int(option) == 1):
					hasEnded = True
					self.event_list.pop(i)

				if(int(option) == 2):
					hasEnded = True
			i += 1

	def is_same_day(self, first_date, second_date):
		if(first_date.day == second_date.day and first_date.month == second_date.month and first_date.year == second_date.year):
			return True
		else:
			return False

if __name__ == "__main__":
	a = Almanaque()
	a.check_arguments()

